#include "InSdkWifiScanner.h"

#define DEF_TAG                 "WifiScan"

#define FALSE   0
#define TRUE    1

typedef struct {
    int encrypt_mode;
    int signal;
    char ssid[32];
    char mac[20];
}SWifiApInfo;

typedef struct {
    int total_cnt;
    SWifiApInfo array[MAX_WIFI_LIST];
}SWifiScanInfo;

typedef int (*CALLBACK)(SWifiApInfo *);

int socket_fd;
char interface[10];
struct iw_range range;
SWifiApInfo new_ap;
SWifiScanInfo *ap_array = NULL;

CALLBACK scan_callback;
static pthread_t pthread_scan_wifi_id = 0;
static pthread_mutex_t mute = PTHREAD_MUTEX_INITIALIZER;
static char is_scanning_wifi = SCAN_WIFI_OFF;

void Ext_InSdkWiFiScan_SetType(int type)
{
    is_scanning_wifi = type;
}

static void process_iw_event(struct stream_descr *stream, struct iw_event *event);

static inline char * get_mac_address(const struct sockaddr *sap, char* bufp)
{
    iw_ether_ntop((const struct ether_addr *) sap->sa_data, bufp);
    return bufp;
}

#if 0
static int printf_one_ap(SWifiApInfo *ap)
{
    printf ("ssid: %s, ", ap->ssid);
    printf ("encrypt_mode: %d, ", ap->encrypt_mode);
    printf ("mac: %s, ", ap->mac);
    printf ("signal: %d ! \n", ap->signal);
    return 0;
}
#endif

static int add_new_ap(SWifiApInfo *ap)
{
    int i;

    pthread_mutex_lock(&mute);

    if (ap_array == NULL)
        goto EXIT_ADD_ONE;

    /*
    printf ("<new> ");
    printf_one_ap(ap);
    */

    if (strlen(ap->ssid) > 0) {
        for(i = 0; i < ap_array->total_cnt; i++) {
            if (strstr(ap->mac, ap_array->array[i].mac) != NULL)
                goto EXIT_ADD_ONE;
        }

        if (ap_array->total_cnt < MAX_WIFI_LIST) {
            memcpy(&ap_array->array[ap_array->total_cnt], ap, sizeof(SWifiApInfo));
            PRT_DBG(DEF_TAG,"add ssid: %s\n",ap_array->array[ap_array->total_cnt].ssid);
            ap_array->total_cnt++;
            //PRT_INFO(DEF_TAG,"total cnt: %d\n",ap_array->total_cnt);
        }
    }

EXIT_ADD_ONE:
    pthread_mutex_unlock(&mute);

    return 0;
}

static int scan_wifi()
{
    struct iwreq      wrq;
    unsigned char *   buffer = NULL;      /* Results */
    int               buflen = IW_SCAN_MAX_DATA; /* Min for compat WE<17 */
    struct timeval    tv;             /* Select timeout */
    int               timeout = 15000000;     /* 15s */
    int               scanflags = IW_SCAN_ALL_ESSID | IW_SCAN_ALL_FREQ | IW_SCAN_ALL_MODE | IW_SCAN_ALL_RATE;		/* Flags for scan */

    wrq.u.data.pointer = NULL;
    wrq.u.data.length = 0;
    wrq.u.data.length = 0;

    tv.tv_sec = 0;
    tv.tv_usec = 250000;

    if(iw_set_ext(socket_fd, interface, SIOCSIWSCAN, &wrq) < 0)
    {
        PRT_NORMAL("Could not start scan: ");
        if (errno == EPERM)
        {
            PRT_NORMAL("Insufficient permissions. THE REPORTED RESULTS WILL BE LEFT_OVERS FROM PREVIOUS SCANS\n");
        }
        else
        {
            if (errno == EBUSY)
            {
                PRT_NORMAL("Device or resource busy.\n");
            }
            else
            {
                PRT_NORMAL("iw_set_ext failed. errno\n");
            }
            sleep(1);
            return -1;
        }
    }

    timeout -= tv.tv_usec;
    int waiting_for_scan_results = TRUE;
    while((waiting_for_scan_results == TRUE) && (is_scanning_wifi != SCAN_WIFI_OFF)) {
        fd_set    rfds;
        int       last_fd;
        int       ret;
        FD_ZERO(&rfds);
        last_fd = -1;
        ret = select(last_fd + 1, &rfds, NULL, NULL, &tv);
        if(ret < 0){
            if(errno == EAGAIN || errno == EINTR)
            {
                continue;
            }
            perror("select error\n");
            PRT_ERR(DEF_TAG,"Unhandled signal errno: %d\n",errno);
            return -1;
        }
        if(ret == 0){
            unsigned char *newbuf;
            while((waiting_for_scan_results == TRUE) && (is_scanning_wifi != SCAN_WIFI_OFF)) {
                newbuf = (unsigned char*)realloc(buffer, buflen);
                if(newbuf == NULL){
                    if(buffer)
                        free(buffer);
                    PRT_ERR(DEF_TAG,"%s: Allocation failed\n", __FUNCTION__);
                    PRT_ERR(DEF_TAG,"Unhandled signal - exiting\n");
                    return -1;
                }
                buffer = newbuf;
                // Try to read the results
                wrq.u.data.pointer = buffer;
                wrq.u.data.flags = scanflags;
                wrq.u.data.length = buflen;
                // send a scan request
                if(iw_set_ext(socket_fd, interface, SIOCGIWSCAN, &wrq) < 0){
                    //process errors
                    // If the buffer was too small to store the scan results, 
                    // we will double the size of the buffr and try again
                    if((errno == E2BIG) && (range.we_version_compiled > 16)
                        && (buflen < 0xFFFF)){
                        if(wrq.u.data.length > buflen)
                        {
                            buflen = wrq.u.data.length;
                        }
                        else
                        {
                            buflen *= 2;
                        }

                        if(buflen > 0xFFFF)
                        {
                            buflen = 0xFFFF;
                        }
                        continue;
                    }
                    // If results are not available, restart timer and try again
                    if(errno == EAGAIN){
                        /* Restart timer for only 100ms*/
                        tv.tv_sec = 0;
                        tv.tv_usec = 250000;
                        timeout -= tv.tv_usec;
                        if( timeout <0){
                            printf("Timed out while waiting for scan resutls");
                            waiting_for_scan_results = FALSE;
                        }
                        break;
                    }
                    // if we got here, there was some error
                    if(buffer)
                        free(buffer);
                    printf("Failed to read scan data, errno: ");
                    return -1;
                }
                else
                {
                    /* We have the results, go to process them */
                    waiting_for_scan_results = FALSE;
                }
            }
        }
    }

    //printf ("--------------------------> In %s: %d !\n", __FUNCTION__, __LINE__);
    // process the scan data
    //PRT_INFO(DEF_TAG,"len: %d,version: %d\n",wrq.u.data.length,range.we_version_compiled);
    if((wrq.u.data.length > 0) && (is_scanning_wifi != SCAN_WIFI_OFF)) {
        struct iw_event       iwe;
        struct stream_descr   stream;
        int           ret;

        iw_init_event_stream(&stream, (char *) buffer, wrq.u.data.length);
        do{
            /* Extract an event and print it */
            ret = iw_extract_event_stream(&stream, &iwe,range.we_version_compiled);
            if(ret > 0)
                process_iw_event(&stream, &iwe);
        }while(ret > 0);
    }

    if (buffer != NULL)
        free(buffer);
    return(0);
}

static inline int iw_print_ie_wpa(unsigned char *	iebuf, int	buflen)
{
    int			ielen = iebuf[1] + 2;
    int			offset = 2;	/* Skip the IE id, and the length. */
    unsigned char		wpa1_oui[3] = {0x00, 0x50, 0xf2};
    unsigned char		wpa2_oui[3] = {0x00, 0x0f, 0xac};
    unsigned char *	wpa_oui;
    //int			i;
    //uint16_t ver = 0;
    //uint16_t		cnt = 0;

    if(ielen > buflen)
        ielen = buflen;

#if 0
    /* Debugging code. In theory useless, because it's debugged ;-) */
    printf("IE raw value %d [%02X", buflen, iebuf[0]);
    for(i = 1; i < buflen; i++)
        printf(":%02X", iebuf[i]);
    printf("]\n");
#endif

    switch(iebuf[0])
    {
    case 0x30:		/* WPA2 */
        /* Check if we have enough data */
        if(ielen < 4)
        {
            //iw_print_ie_unknown(iebuf, buflen);
            return 1;
        }

        wpa_oui = wpa2_oui;
        break;

    case 0xdd:		/* WPA or else */
        wpa_oui = wpa1_oui;

        /* Not all IEs that start with 0xdd are WPA.
       * So check that the OUI is valid. Note : offset==2 */
        if((ielen < 8)
                || (memcmp(&iebuf[offset], wpa_oui, 3) != 0)
                || (iebuf[offset + 3] != 0x01))
        {
            //iw_print_ie_unknown(iebuf, buflen);
            return 1;
        }

        /* Skip the OUI type */
        offset += 4;
        break;

    default:
        return 1;
    }

    /* Pick version number (little endian) */
    //ver = iebuf[offset] | (iebuf[offset + 1] << 8);
    offset += 2;

    if(iebuf[0] == 0xdd) {
        //printf("WPA Version %d\n", ver);
        return 0;
    }
    if(iebuf[0] == 0x30) {
        //printf("IEEE 802.11i/WPA2 Version %d\n", ver);
        return 0;
    }

    return 1;
}

static inline void iw_print_gen_ie(unsigned char *buffer, int buflen)
{
    int offset = 0;

    /* Loop on each IE, each IE is minimum 2 bytes */
    while(offset <= (buflen - 2))
    {
        /* Check IE type */
        switch(buffer[offset])
        {
        case 0xdd:	/* WPA1 (and other) */
        case 0x30:	/* WPA2 */
            if (iw_print_ie_wpa(buffer + offset, buflen) == 0) {
                new_ap.encrypt_mode = WIFI_ENCRYPT_MODE_WPA2;
                return;
            } else
                new_ap.encrypt_mode = WIFI_ENCRYPT_MODE_WEP;

            break;
        default:
            break;
        }
        /* Skip over this IE to the next one in the list. */
        offset += buffer[offset+1] + 2;
    }
}

static void process_iw_event(struct stream_descr *	stream,	/* Stream of events */
                      struct iw_event *		event	/* Extracted token */)
{
    char buffer[128];    /* Temporary buffer */
    char essid[IW_ESSID_MAX_SIZE+1];

    /* Now, let's decode the event */
    switch(event->cmd) {
    case SIOCGIWAP:
        // if ap already points to something, then it means we finished processing an AP
        if (new_ap.encrypt_mode != WIFI_ENCRYPT_MODE_UNKONW) {
            scan_callback(&new_ap);
        }
        memset(&new_ap, 0, sizeof(new_ap));
        char *mac_addr = get_mac_address(&event->u.ap_addr, buffer);
        memcpy(new_ap.mac, mac_addr, strlen(mac_addr));
        break;
    case SIOCGIWESSID:
        memset(essid, '\0', sizeof(essid));
        if((event->u.essid.pointer) && (event->u.essid.length))
            memcpy(essid, event->u.essid.pointer, event->u.essid.length);
        if(event->u.essid.flags)
            memcpy(new_ap.ssid, essid, event->u.essid.length);
        else
            memset(new_ap.ssid, 0, sizeof(new_ap.ssid));
        break;
    case SIOCGIWENCODE:
        if(event->u.data.flags & IW_ENCODE_DISABLED)
            new_ap.encrypt_mode = WIFI_ENCRYPT_MODE_OPEN;
        else
            new_ap.encrypt_mode = WIFI_ENCRYPT_MODE_WEP;
        break;
    case IWEVGENIE:
        /* 高拓的wifi模块只执行一次事件，buffer会很大；而必联的wifi模块会分多次调用，每次的buffer小。 */
        if (new_ap.encrypt_mode == WIFI_ENCRYPT_MODE_WEP)
            iw_print_gen_ie(event->u.data.pointer, event->u.data.length);
        break;
    case SIOCGIWFREQ:
        //(*ap)->frequency = iw_freq2float(&(event->u.freq));
        break;
    case IWEVQUAL:
        if (event->u.qual.level != 0 || (event->u.qual.updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) {
            new_ap.signal = event->u.qual.level;
            //new_ap.signal = event->u.qual.qual;
        } /*else {
            (*ap)->signal_updated = FALSE;
        }*/
        break;
    default:
        break;
    }	/* switch(event->cmd) */
}

static void *pthread_scan_wifi(void *param)
{
    while(is_scanning_wifi != SCAN_WIFI_OFF)
    {
        if (is_scanning_wifi == SCAN_WIFI_ON)
        {
            is_scanning_wifi = SCAN_WIFI_SCANING;
        }
        scan_wifi();
    }

    return NULL;
}

int Ext_InSdkWiFiScan_InitInternel(char *wifi_name)
{
    pthread_mutex_lock(&mute);
    if (ap_array != NULL) {
        printf ("%s: ap_array has exsit ... \n", __FILE__);
        pthread_mutex_unlock(&mute);
        return 1;
    }
    ap_array = malloc(sizeof(SWifiScanInfo));
    if (ap_array == NULL) {
        printf ("%s:%d No enough memory ... \n", __FILE__, __LINE__);
        pthread_mutex_unlock(&mute);
        return -1;
    }
    memset(ap_array, 0, sizeof(SWifiScanInfo));
    is_scanning_wifi = SCAN_WIFI_SCANING;

    socket_fd = iw_sockets_open();
    scan_callback = add_new_ap;

    memset(interface, 0, sizeof(interface));
    memcpy(interface, wifi_name, strlen(wifi_name));

    if(socket_fd < 0){
        printf("Could not open socket. Errno: %d", errno);
        goto SET_IW_FAIL;
    }
    if (iw_get_range_info(socket_fd, interface, &range) < 0){
        printf("Could not start scanning, iw_get_range_info failed. Errno: %d\n", errno);
        goto SET_IW_FAIL;
    }

    if(pthread_create(&pthread_scan_wifi_id, NULL, pthread_scan_wifi, NULL) != 0) {
        perror("create pthread fail: ");
    }
    pthread_detach(pthread_scan_wifi_id);

    pthread_mutex_unlock(&mute);

    return 0;

SET_IW_FAIL:
    if (ap_array != NULL) {
        free(ap_array);
        ap_array = NULL;
    }
    is_scanning_wifi = SCAN_WIFI_OFF;
    pthread_mutex_unlock(&mute);
    return -1;
}

int Ext_InSdkWiFiScan_Stop(void)
{
    pthread_mutex_lock(&mute);
    if (is_scanning_wifi == SCAN_WIFI_ON) {
        pthread_mutex_unlock(&mute);
        return -1;
    }

    if (ap_array != NULL) {
        free(ap_array);
        ap_array = NULL;
    }

    is_scanning_wifi = SCAN_WIFI_OFF;

    if (pthread_scan_wifi_id != 0) {
        pthread_join(pthread_scan_wifi_id, NULL);
    }

    pthread_scan_wifi_id = 0;

    pthread_mutex_unlock(&mute);

    return 0;
}

int Ext_InSdkWiFiScan_CheckEncrypt(char *ssid)
{
    int i, cnt, ret = -1;
    pthread_mutex_lock(&mute);
    if ((ap_array != NULL) && (is_scanning_wifi != SCAN_WIFI_OFF)) {
        cnt = ap_array->total_cnt;
        for (i = 0; i < cnt; i++) {
            if (strcmp(ap_array->array[i].ssid, ssid) == 0) {
                //printf ("%s -> %s \n", ap_array->array[i].ssid, ssid);
                ret = ap_array->array[i].encrypt_mode;
                break;
            }
        }
    }
    pthread_mutex_unlock(&mute);

    return ret;
}

int Ext_InSdkWiFiScan_SaveCfgFile(const char *filepath, char *ssid, char *passwd, int cfgfile_type)
{
    FILE *fd;
    char commset[]={"update_config=1\nctrl_interface=/var/run/wpa_supplicant\neapol_version=1\nfast_reauth=1\n"};
    char WPAstr[]={"ap_scan=1\nnetwork={\n\tssid=\"%s\"\n\tscan_ssid=1\n\tpsk=\"%s\"\n}\n"};
    char OPENstr[]={"ap_scan=1\nnetwork={\n\tssid=\"%s\"\n\tkey_mgmt=NONE\n\tscan_ssid=1\n\tauth_alg=OPEN\n}\n"};
    char WEPstr_hex[]={"ap_scan=1\nnetwork={\n\tssid=\"%s\"\n\tkey_mgmt=NONE\n\tauth_alg=OPEN SHARED\n\twep_key0=%s\n\twep_tx_keyidx=0\n\tscan_ssid=1\n}\n"};
    char WEPstr_ascii[]={"ap_scan=1\nnetwork={\n\tssid=\"%s\"\n\tkey_mgmt=NONE\n\tauth_alg=OPEN SHARED\n\twep_key0=\"%s\"\n\twep_tx_keyidx=0\n\tscan_ssid=1\n}\n"};
    char CmdStr[1024] = {0};

    fd=fopen(filepath, "w+");
    if( fd == NULL )
    {
        printf("file open can not create file !!! \n");
        return -ENOENT;
    }

    memcpy(CmdStr, commset, sizeof(commset));
    fprintf(fd,"%s", CmdStr);

    switch (cfgfile_type)
    {
    case WIFI_ENCRYPT_MODE_OPEN:
    {
        sprintf(CmdStr, OPENstr, ssid);
    }
        break;

    case WIFI_ENCRYPT_MODE_WPA2:
    {
        sprintf(CmdStr, WPAstr, ssid, passwd);
    }
        break;

    case WIFI_ENCRYPT_MODE_WEP:
    {
        if ((strlen(passwd) == 5) || (strlen(passwd) == 13))
        {
            sprintf(CmdStr, WEPstr_ascii, ssid, passwd);
        }
        else if ((strlen(passwd) == 10) || (strlen(passwd) == 26))
        {
            sprintf(CmdStr, WEPstr_hex, ssid, passwd);
        }
        else
        {
            /* 如果密码长度不对，则认为是wpa的加密方式 */
            sprintf(CmdStr, WPAstr, ssid, passwd);
        }
    }
        break;

    default :
    {
        printf ("Unknow encrypt type...\n");
        fclose(fd);
        remove(filepath);
        return -EINVAL;
    }
        break;
    }
//    printf("%s\n",CmdStr);
    fprintf(fd,"%s", CmdStr);
    fclose(fd);

    return 0;
}

void Ext_InSdkWiFiScan_SaveHideCfgFile(char *ssid, char *passwd)
{
    int ret;
    ret = Ext_InSdkWiFiScan_SaveCfgFile(DEF_OPEN_CONF_PATH,ssid,passwd,WIFI_ENCRYPT_MODE_OPEN);
    if (ret != 0)
    {
        PRT_ERR(DEF_TAG,"wifi scan open error !!!!\n");
    }

    ret = Ext_InSdkWiFiScan_SaveCfgFile(DEF_WPA_CONF_PATH,ssid,passwd,WIFI_ENCRYPT_MODE_WPA2);
    if (ret != 0)
    {
        PRT_ERR(DEF_TAG,"wifi scan wpa error !!!! \n");
    }

    ret = Ext_InSdkWiFiScan_SaveCfgFile(DEF_WEP_CONF_PATH,ssid,passwd,WIFI_ENCRYPT_MODE_WEP);
    if (ret != 0)
    {
        PRT_ERR(DEF_TAG,"wifi scan wep error !!!! \n");
    }
}
